
local function _init_lspc(lspc)
	function lspc:on_locate_file(file_name, begin_line, begin_col, end_line, end_col)
		mgr:on_locate_file(1, file_name, begin_line, begin_col, end_line, end_col);
	end;
	function lspc:on_diagnoistic(src, code, file, begin_line, begin_col, end_line, end_col, info)
		if src == "LSP" and begin_line == 0 and end_line == 0 and info == "" then
			if code == "begin" then
				mgr:clear_doc_diagnoistic("diag", file);
			elseif code == "end" then
				-- refresh the current document
				local doc = mgr.current_document;
				if doc.file_name == file then
					doc:reset_render();
				end;
			end;
		else
			-- print(file,":", begin_line, "," , begin_col, ":", end_line, ",", end_col, ": diag ", info);
			mgr:add_doc_diagnoistic("diag", file, begin_line, begin_col, end_line, end_col, 4, info);
		end;
	end;
	return lspc;
end;

lsp = {
	_init_clangd = function(self, cmd)
		local clangd_path = CLANGD_PATH;
		local lspc = _init_lspc(edx:create_lsp_client());
		edx._lsp_clients["cpp"] = lspc;
		local build_path = cmd.build_path;
		local toolset = cmd.toolset;
		local query_driver = toolset.home;
		local lsp_env = {["CPATH"] = table.concat(toolset.include, ";"); ["PATH"]=table.concat(toolset.bin, ";")};
		if toolset.query_driver then
			query_driver = toolset:query_driver(cmd);
		elseif toolset.tools["clang"] then
			query_driver = make_path(query_driver, "**\\clang++*");
		elseif toolset.tools["gcc"] then
			query_driver = make_path(query_driver, "**\\g++*");
		end;
		lspc.send_empty_command = 1;
		local param_compile_commands_dir = "";
		local param_query_driver = "";
		if build_path then
			param_compile_commands_dir = [[--compile-commands-dir="]]..build_path..[["]];
		end;
		if query_driver then
			param_query_driver = [[ --query-driver="]]..query_driver..[["]];
		end;
		if toolset.wsl then
			-- remap wsl path for compile_commands.json
			local query_driver_src = make_path(edx.app_path, "wsl-query-driver.exe");
			local new_driver_time = utils:file_time(query_driver_src);
			if new_driver_time == nil then
				query_driver_src = make_path(edx.startup_path, "wsl-query-driver.exe");
				new_driver_time = utils:file_time(query_driver_src);
			end;
			local query_driver = make_path(build_path, "../wsl-"..toolset.wsl.."-gcc.exe");
			local old_driver_time = utils:file_time(query_driver);
			if old_driver_time == nil or new_driver_time > old_driver_time then
				if old_driver_time ~= nil then
					os.remove(query_driver);
				end;
				print("Install clangd query driver... ", query_driver);
				utils:copy_file(query_driver_src, query_driver);
			end;
			query_driver = query_driver:gsub("\\", "/");
			local org_file_name = make_path(build_path, "/compile_commands.json");
			local new_file_name = org_file_name..".org";
			os.rename(org_file_name, new_file_name);
			local org_file = io.open(new_file_name);
			local mapped_file = io.open(org_file_name, "w+");
			local unmap_path = toolset.unmap_path;
			for ln in org_file:lines() do
				ln = string.gsub(ln, "-I/mnt/(%w)/", "-I%1:/");
				ln = string.gsub(ln, "-c /mnt/(%w)/", "-c %1:/");
				ln = string.gsub(ln, "\"/mnt/(%w)/", "\"%1:/");
				ln = string.gsub(ln, "\"/[^/]+/.*/g[%+c][%+c]", "\""..query_driver);
				ln = string.gsub(ln, "-isystem (/[%w/%-_%./]*)", function(path)
						return [[-isystem ]]..(string.gsub(unmap_path(path), "\\", "\\\\"));
				end);
				ln = string.gsub(ln, "-I(/[%w/%-_%./]*)", function(path)
						return [[-I]]..(string.gsub(unmap_path(path), "\\", "\\\\"));
				end);
				mapped_file:write(ln, "\n");
			end;
			org_file:close();
			mapped_file:close();
			os.remove(new_file_name);
			param_query_driver = [[ --query-driver="]]..query_driver..[["]];
		elseif toolset.remote then
			-- remap host path for compile_commands.json
			local query_driver_src = make_path(edx.app_path, "wsl-query-driver.exe");
			local new_driver_time = utils:file_time(query_driver_src);
			if new_driver_time == nil then
				query_driver_src = make_path(edx.startup_path, "wsl-query-driver.exe");
				new_driver_time = utils:file_time(query_driver_src);
			end;
			local query_driver = make_path(build_path, "../ssh-gcc.exe");
			local old_driver_time = utils:file_time(query_driver);
			if old_driver_time == nil or new_driver_time > old_driver_time then
				if old_driver_time ~= nil then
					os.remove(query_driver);
				end;
				print("Install clangd query driver... ", query_driver);
				utils:copy_file(query_driver_src, query_driver);
			end;
			query_driver = query_driver:gsub("\\", "/");
			local org_file_name = make_path(build_path, "/compile_commands.json");
			local new_file_name = org_file_name..".org";
			os.rename(org_file_name, new_file_name);
			local org_file = io.open(new_file_name);
			local mapped_file = io.open(org_file_name, "w+");
			local unmap_path = toolset.unmap_path;

			dbg_call(function()
					local query_driver_target = make_path(build_path, "../target.txt");
					target_file = io.open(query_driver_target, "w+");
					local local_include_path = {};
					for idx, path in ipairs(toolset.include) do
						table.insert(local_include_path, unmap_path(path));
					end;
					target_file:write(
						"Target: "..toolset.target.."\n"..
						"Thread model: posix\n"..
						"Supported LTO compression algorithms: zlib\n"..
						"gcc version "..toolset.version.."\n"..
						"#include \"...\" search starts here:\n"..
						"#include <...> search starts here:\n"..
						" "..
						table.concat(local_include_path, "\n ")..
						"\n"..
						"End of search list.\n\n"
					);
					target_file:close();
			end, "");

			for ln in org_file:lines() do
				ln = string.gsub(ln, "-isystem (/[%w/%-_%./]*)", function(path)
						return [[-isystem ]]..(string.gsub(unmap_path(path), "\\", "\\\\"));
				end);
				ln = string.gsub(ln, "-I(/[%w/%-_%./]*)", function(path)
						return [[-I]]..(string.gsub(unmap_path(path), "\\", "\\\\"));
				end);
				ln = string.gsub(ln, "-c (/[%w/%-_%./]*)", function(path)
						return [[-c ]]..(string.gsub(unmap_path(path), "\\", "\\\\"));
				end);
				ln = string.gsub(ln, "\"command\":%s+\"(/%S+)%s", function(path)
						return [["command": "]]..(string.gsub(query_driver, "\\", "\\\\"))..[[ ]];
				end);
				ln = string.gsub(ln, "\"file\":%s+\"(.+)\"", function(path)
						return [["file": "]]..(string.gsub(unmap_path(path), "\\", "\\\\"))..[["]];
				end);
				ln = string.gsub(ln, "\"directory\":%s+\"(.+)\"", function(path)
						return [["directory": "]]..(string.gsub(unmap_path(path), "\\", "\\\\"))..[["]];
				end);
				mapped_file:write(ln, "\n");
			end;
			org_file:close();
			mapped_file:close();
			os.remove(new_file_name);
			param_query_driver = [[ --query-driver="]]..query_driver..[["]];
		end;
		if toolset["type"] == "esp32-idf" then
			-- remap esp path for compile_commands.json
			local query_driver_src = make_path(edx.app_path, "wsl-query-driver.exe");
			local new_driver_time = utils:file_time(query_driver_src);
			if new_driver_time == nil then
				query_driver_src = make_path(edx.startup_path, "wsl-query-driver.exe");
				new_driver_time = utils:file_time(query_driver_src);
			end;

			local query_driver_target = make_path(build_path, "../target.txt");
			target_file = io.open(query_driver_target, "w+");
			target_file:write(query_driver);
			target_file:close();
			
			local query_driver = make_path(build_path, "../esp32-none-gcc.exe");
			local old_driver_time = utils:file_time(query_driver);
			if old_driver_time == nil or new_driver_time > old_driver_time then
				if old_driver_time ~= nil then
					os.remove(query_driver);
				end;
				print("Install clangd query driver... ", query_driver);
				utils:copy_file(query_driver_src, query_driver);
			end;
			query_driver = query_driver:gsub("\\", "/");
			local org_file_name = make_path(build_path, "/compile_commands.json");
			local new_file_name = org_file_name..".org";
			os.rename(org_file_name, new_file_name);
			local org_file = io.open(new_file_name);
			local mapped_file = io.open(org_file_name, "w+");
			local unmap_path = toolset.unmap_path;
			for ln in org_file:lines() do
				ln = string.gsub(ln, [[command":%s*".-\\.-%-g[%+c][%+c]%.exe]], "command\": \""..query_driver.. [[ ]]..toolset["define"]);
				ln = string.gsub(ln, "\"file\":%s+\"(.+)\"", function(path)
						return [["file": "]]..(string.gsub(unmap_path(path), "\\", "\\\\"))..[["]];
				end);
				ln = string.gsub(ln, "\"directory\":%s+\"(.+)\"", function(path)
						return [["directory": "]]..(string.gsub(unmap_path(path), "\\", "\\\\"))..[["]];
				end);
				mapped_file:write(ln, "\n");
			end;
			org_file:close();
			mapped_file:close();
			os.remove(new_file_name);
			param_query_driver = [[ --query-driver="]]..query_driver..[["]];
		end;
		lspc:init(
			"ClangD",
			clangd_path,
			[[--pch-storage=memory ]]..
			[[--all-scopes-completion ]]..
			-- [[--completion-style=detailed ]]..
			[[--completion-style=bundled ]]..
			[[--include-ineligible-results ]]..
			-- [[--j=8 ]]..
			-- [[--async-preamble ]]..
			[[--limit-results=200 ]]..
			[[--header-insertion-decorators=0 ]]..
			-- [[--sync ]]..
			-- [[--log=verbose ]]..
			[[--background-index=1 ]]..
			param_compile_commands_dir..
			param_query_driver
			, cmd.project_path
			, lsp_env
		);
	end;
	_init_ccls = function(self, cmd)
		local ccls_root = [[D:\Tools\ccls]];
		local ccls_path = make_path(ccls_root, "ccls.exe");
		local lspc = _init_lspc(edx:create_lsp_client());
		local lsp_env = nil;
		edx._lsp_clients["cpp"] = lspc;
		local build_path = cmd.build_path;
		local toolset = cmd.toolset;
		lspc.send_empty_command = 2;
		local compile_commands = make_path(build_path, "/compile_commands.json");

		local ccls_config = {
			cache = {
				directory = make_path(build_path,".ccls-cache");
			};
			clang = {
				resourceDir = make_path(ccls_root, "clang/14.0.6");
			};
			compilationDatabaseDirectory = build_path;
		};
		local ccls_config_json = utils:tojson(ccls_config);
		
		lspc:init(
			"CCLS",
			ccls_path,
			[[--init=]]..(ccls_config_json:gsub("\"", [["""]]))
			.. [[ -v=1]]
			-- ..[[ --log-file=]]..make_path(build_path, "/ccls-log.txt")
			, cmd.project_path
			, lsp_env
		);
	end;
	_init_serve_d = function(self, cmd)
		local lspc = _init_lspc(edx:create_lsp_client());
		local lsp_env = nil;
		edx._lsp_clients["d"] = lspc;
		lspc:init(
			"Serve-D",
			[[D:\dmd2\windows\bin\serve-d.exe]],
			[[]]
			, cmd.project_path
			, lsp_env
		);
	end;
	_init_lua_lang_server = function(self,cmd)
		local lspc = _init_lspc(edx:create_lsp_client());
		local lsp_env = nil;
		local project_path = cmd and cmd.project_path;
		-- local project_path = make_path(mgr.current_document.file_name, "../");
		edx._lsp_clients["lua"] = lspc;
		lspc.send_empty_command = 2;
		lspc:init(
			"Lua-Language-Server",
			[[D:\Tools\lua-language-server-3.5.3\bin\lua-language-server.exe]],
			[[-E D:/Tools/lua-language-server-3.5.3/main.lua]]
			, project_path
			, lsp_env
		);
	end;
	_init_lua_helper_lsp = function(self,cmd)
		local lspc = _init_lspc(edx:create_lsp_client());
		local lsp_env = nil;
		local project_path = cmd and cmd.project_path;
		edx._lsp_clients["lua"] = lspc;
		lspc.send_empty_command = 2;
		lspc:init(
			"LuaHelper-LSP",
			[[luahelper-lsp.exe]],
			[[-localpath project_path ]]..
			[[-mode 1]]
			, project_path
			, lsp_env
		);
	end;
	_init_java_lang_server = function(self,cmd)
		local lspc = _init_lspc(edx:create_lsp_client());
		local lsp_env = nil;
		local project_path = cmd and cmd.project_path;
		-- local project_path = make_path(mgr.current_document.file_name, "../");
		edx._lsp_clients["java"] = lspc;
		lspc.send_empty_command = 2;
		lspc:init(
			"JDT-Languate-Server",
			[[java]],
			[[ -Declipse.application=org.eclipse.jdt.ls.core.id1]]..
			[[ -Dosgi.bundles.defaultStartLevel=4]]..
			[[ -Declipse.product=org.eclipse.jdt.ls.core.product]]..
			[[ -Dlog.level=ALL -noverify -Xmx1G]]..
			[[ -jar D:\jdt-language-server\plugins\org.eclipse.equinox.launcher_1.6.400.v20210924-0641.jar]]..
			[[ -configuration D:\jdt-language-server\config_win]]..
			[[ -data D:\jdt-language-server\data]]..
			[[ --add-modules=ALL-SYSTEM]]..
			[[ --add-opens java.base/java.util=ALL-UNNAMED]]..
			[[ --add-opens java.base/java.lang=ALL-UNNAMED]]
			, project_path
			, lsp_env
		);
		
	end;
	_completion_exec = function(self, doc, func)
		if not edx._lsp_clients then
			return false;
		end;
		local content_type_id = content_type_name[doc.content_type];
		local lspc = edx._lsp_clients[content_type_id];
		if not lspc then
			return false;
		end;
		return dbg_call( function()
				return func(lspc);
		end, "lsp error");
	end;
	_try_init_lsp = function(self, working_dir)
		if edx._lsp_clients then
			return;
		end;
		local cmd = find_build_command();
		if cmd and cmd.type ~= "direct" then
			return;
		end;
		if not working_dir then
			working_dir = "";
		end;
		if not cmd then
			cmd = {
				build_path = "";
				toolset = TOOLSETS[1];
				project_path = working_dir;
			};
		end;
		if not cmd.toolset then
			cmd.toolset = TOOLSETS[1];
		end;
		if not cmd.build_path then
			cmd.build_path = "";
		end;
		if not cmd.project_path then
			cmd.project_path = working_dir;
		end;
		self:reset(cmd);
	end;
	terminate = function(self)
		if not edx._lsp_clients then
			return;
		end;
		for name, lspc in pairs(edx._lsp_clients) do
			lspc:terminate();
		end;

		edx._lsp_clients = nil;
	end;
	reset = function(self,cmd)
		self:terminate();
		edx._lsp_clients = {};
		edx:clear("lsp");

		-- init clangd service
		self:_init_clangd(cmd);
		-- self:_init_ccls(cmd);

		-- init luahelper-lsp
		-- self:_init_lua_helper_lsp(cmd);

		-- init serve-d service
		-- self:_init_serve_d(cmd);
		
		-- self:_init_lua_lang_server(cmd);

		-- self:_init_java_lang_server(cmd);

		local all_doc = {mgr.get_all_documents()};
		local def_target = menu_bar.get_cmake_default_target();
		for idx, doc in ipairs(all_doc) do
			local content_type_id = content_type_name[doc.content_type];
			local lspc = edx._lsp_clients[content_type_id];
			if lspc then
				lspc:did_open_doc(doc, content_type_id);
			end;
		end;
	end;
	doc_saved = function(self, doc)
		self:_completion_exec(doc, function(lspc) return lspc:did_save_doc(doc); end);
	end;
	doc_opened = function(self, doc)
		self:_try_init_lsp();
		local content_type_id = content_type_name[doc.content_type];
		self:_completion_exec(doc, function(lspc) return lspc:did_open_doc(doc, content_type_id); end);
	end;
	doc_closed = function(self, doc)
		self:_completion_exec(doc, function(lspc) return lspc:did_close_doc(doc); end);
	end;
	did_rename_doc = function(self, doc, from_path, to_path)
		self:_completion_exec(doc, function(lspc) return lspc:did_rename_doc(doc, from_path, to_path); end);
	end;
	find_signature_help = function(self, doc, helper)
		if self:_completion_exec(doc, function(lspc) return lspc:find_signature_help(doc, helper) or (helper.cancel_completion() and false); end) then
			return true;
		end;
		return false;
	end;
	do_completion = function(self, doc, helper)
		if self:_completion_exec(doc, function(lspc) return lspc:do_complete(doc, helper) or (helper.cancel_completion() and false); end) then
			return true;
		end;
		return false;
	end;
	update_completion_tip = function(self, doc, helper)
		if self:_completion_exec(doc, function(lspc) return lspc:update_completion_list_tip(doc, helper) or (helper.cancel_completion() and false); end) then
			return true;
		end;
		return false;
	end;
	cleanup_completion = function(self, doc)
		if edx._lsp_clients then
			if doc then
				local content_type_id = content_type_name[doc.content_type];
				local lspc = edx._lsp_clients[content_type_id];
				if lspc then
					lspc:clean_completion_list();
				end;
			else
				for idx, lspc in ipairs(edx._lsp_clients) do
					lspc:clean_completion_list();
				end;
			end;
		end;
	end;
	commit_completion = function(self, doc, helper)
		if(self:_completion_exec(doc, function(lspc) return lspc:commit_completion(doc, helper); end)) then
			return true;
		end;
		return false;
	end;
	jump_placeholder = function(self, doc)
		if(self:_completion_exec(doc, function(lspc) return lspc:jump_placeholder(doc); end)) then
			return true;
		end;
		return false;
	end;
	hover_info = function(self, doc, line, col)
		if line == nil then
			line = -1;
		end;
		if col == nil then
			col = -1;
		end;
		if(self:_completion_exec(doc, function(lspc) return lspc:find_hover_info(doc, line, col); end)) then
			return true;
		end;
		return false;
	end;
	find_declaration = function(self, doc)
		if(self:_completion_exec(doc, function(lspc) return lspc:find_declaration(doc); end)) then
			return true;
		end;
		return false;
	end;
	find_definition = function(self, doc)
		if(self:_completion_exec(doc, function(lspc) return lspc:find_definition(doc); end)) then
			return true;
		end;
		return false;
	end;
	find_implementation = function(self, doc)
		if(self:_completion_exec(doc, function(lspc) return lspc:find_implementation(doc); end)) then
			return true;
		end;
		return false;
	end;
	find_reference = function(self, doc)
		if(self:_completion_exec(doc, function(lspc) return lspc:find_reference(doc); end)) then
			return true;
		end;
		return false;
	end;
	list_doc_structure = function(self, doc, class_view)
		if(self:_completion_exec(doc, function(lspc) return lspc:list_doc_structure(doc, class_view); end)) then
			return true;
		end;
		return false;
	end;
};
